/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.utils.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.io.output.ProxyWriter;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.utils.DelegatingServletOutputStream;

/**
 * Portlet response wrapper. Makes sure the portlet doesn't screw with the portal's response
 *
 * <p>Any mutator method calls are not passed on and logged at warn level. The writer/stream objects
 * discard all data written and log byte/char counts at warn level on close.
 */
public class PortletHttpServletResponseWrapper extends AbstractHttpServletResponseWrapper {
    private final IPortletWindow portletWindow;
    private ServletOutputStream servletOutputStream;
    private PrintWriter printWriter;

    public PortletHttpServletResponseWrapper(
            HttpServletResponse httpServletResponse, IPortletWindow portletWindow) {
        super(httpServletResponse);
        this.portletWindow = portletWindow;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (this.servletOutputStream == null) {
            final OutputStream out;
            if (logger.isDebugEnabled()) {
                out =
                        new ByteArrayOutputStream() {
                            @Override
                            public void close() throws IOException {
                                super.close();
                                final byte[] data = this.toByteArray();
                                if (data.length > 0) {
                                    logger.warn(
                                            "Ignored {} bytes written to ServletOutputStream by {}\n\n{}",
                                            data.length,
                                            portletWindow,
                                            new String(data, StandardCharsets.UTF_8));
                                }
                            }
                        };
            } else {
                out =
                        new CountingOutputStream(NullOutputStream.NULL_OUTPUT_STREAM) {
                            @Override
                            public void close() throws IOException {
                                super.close();
                                final long byteCount = this.getByteCount();
                                if (byteCount > 0) {
                                    logger.warn(
                                            "Ignored {} bytes written to ServletOutputStream by {}, turn on DEBUG logging to see the output",
                                            byteCount,
                                            portletWindow);
                                }
                            }
                        };
            }

            this.servletOutputStream = new DelegatingServletOutputStream(out);
        }

        return this.servletOutputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (this.printWriter == null) {
            if (logger.isDebugEnabled()) {
                this.printWriter =
                        new PrintWriter(
                                new StringWriter() {
                                    @Override
                                    public void close() throws IOException {
                                        super.close();
                                        final String data = this.toString();
                                        if (data.length() > 0) {
                                            logger.warn(
                                                    "Ignored {} chars written to PrintWriter by {}\n\n{}",
                                                    data.length(),
                                                    portletWindow,
                                                    data);
                                        }
                                    }
                                });
            } else {
                this.printWriter =
                        new PrintWriter(
                                new ProxyWriter(NullWriter.NULL_WRITER) {
                                    private long count = 0;

                                    @Override
                                    public void close() throws IOException {
                                        super.close();
                                        if (count > 0) {
                                            logger.warn(
                                                    "Ignored {} chars written to PrintWriter by {}, turn on DEBUG logging to see the output",
                                                    count,
                                                    portletWindow);
                                        }
                                    }

                                    @Override
                                    protected void afterWrite(int n) {
                                        count += n;
                                    }
                                });
            }
        }

        return this.printWriter;
    }

    @Override
    public void flushBuffer() throws IOException {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.flushBuffer() from {}", portletWindow);
    }

    @Override
    public void resetBuffer() {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.resetBuffer() from {}", portletWindow);
    }

    @Override
    public void reset() {
        this.logger.warn("Ignoring call to HttpServletResponse.reset() from {}", portletWindow);
    }

    @Override
    public void setBufferSize(int size) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setBufferSize({}) from {}",
                size,
                portletWindow);
    }

    @Override
    public void setContentType(String type) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setContentType({}) from {}",
                type,
                portletWindow);
    }

    @Override
    public void setStatus(int sc) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setStatus({}) from {}", sc, portletWindow);
    }

    @Override
    public void setStatus(int sc, String sm) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setStatus({}, {}) from {}",
                sc,
                sm,
                portletWindow);
    }

    @Override
    public void addCookie(Cookie cookie) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.addCookie({}) from {}",
                cookie,
                portletWindow);
    }

    @Override
    public void sendError(int sc, String msg) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.sendError({}, {}) from {}",
                sc,
                msg,
                portletWindow);
    }

    @Override
    public void sendError(int sc) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.sendError({}) from {}", sc, portletWindow);
    }

    @Override
    public void sendRedirect(String location) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.sendRedirect({}) from {}",
                location,
                portletWindow);
    }

    @Override
    public void setCharacterEncoding(String charset) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setCharacterEncoding({}) from {}",
                charset,
                portletWindow);
    }

    @Override
    public void setDateHeader(String name, long date) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setDateHeader({}, {}) from {}",
                name,
                date,
                portletWindow);
    }

    @Override
    public void addDateHeader(String name, long date) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.addDateHeader({}, {}) from {}",
                name,
                date,
                portletWindow);
    }

    @Override
    public void setHeader(String name, String value) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setHeader({}, {}) from {}",
                name,
                value,
                portletWindow);
    }

    @Override
    public void setContentLength(int len) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setContentLength({}) from {}",
                len,
                portletWindow);
    }

    @Override
    public void addHeader(String name, String value) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.addHeader({}, {}) from {}",
                name,
                value,
                portletWindow);
    }

    @Override
    public void setIntHeader(String name, int value) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setIntHeader({}, {}) from {}",
                name,
                value,
                portletWindow);
    }

    @Override
    public void addIntHeader(String name, int value) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.addIntHeader({}, {}) from {}",
                name,
                value,
                portletWindow);
    }

    @Override
    public void setLocale(Locale loc) {
        this.logger.warn(
                "Ignoring call to HttpServletResponse.setLocale({}) from {}", loc, portletWindow);
    }
}
